'''
Created on Nov 23, 2011

@author: conan
'''
import itertools
from utils import saveObj
import heapq


   
class Task(object):
    '''
    @ivar succs: 
    @type succs: C{list} of L{Task}
    @ivar input: the input of a task is automatically set to the output of the previously
    completed task at runtime.
    @ivar output: when a task is completed, its output will be passed onto the next running 
    task whether it will be used by the next task or not.
    '''
    
    def __init__(self, name):
        self.sf = None
        self.name = name
        self.succs = []
        self.numExec = 0
        self.input = None
        self.output = None
        
    
    
    def run(self):
        '''
        Execute the service() function 
        @return: the succs task ready to run
        '''   
        if self.sf.debug: 
            print 'RUNNING %s LAST %s  ACTIVE %s' % (self, self.sf.lastRun, self.sf.active)
            
        self.numExec += 1
        self.output = self.__call__()
        for t, condition in self.succs:
            #If only the precondition is satisfied
            if condition is True or condition():
                t.input = self.output
                return t
        return None

    
    def __repr__(self):
        return 'Task({})'.format(self.name)

    def __call__(self):
        raise NotImplementedError('__call__ method of %s is not defined' % self)
        
  
#===========================================================================
# Services 
#===========================================================================
    
class Selection(Task):
    '''
    Selection service
    @ivar pd: the predicate function pd(current,j) 
    '''
    def __init__(self, name):
        Task.__init__(self, name)
        self.pd = None
        self.jobs = None


    
    def __call__(self):
        rv = []
        for j in self.jobs:
            if self.pd(self.sf.sp.current, j):
                rv.append(j)
        if self.sf.debug: 
            print 'selected', rv            
        return rv
        
class Dispatch(Task):  
    '''
    Dispatch jobs by a given rule
    '''   
    def __init__(self, name):
        Task.__init__(self, name)
        self.rule = Dispatch.SPT
        self.pd = None
    
    def __call__(self):
        jobs = [j for j in self.sp.J if self.pd(self.sf.sp.current, j)]
        if len(jobs) < 1:
            self.sf.sp.current.process()
        else:
            selected = self.rule(jobs, current=self.sf.sp.current)
            self.sf.sp.current = self.sf.sp.current.allocate(selected)
            self.sf.stat.generated += 1
            self.sf.stat.expanded += 1
        if self.sf.sp.current.isGoal():
            self.sf.sp.best = self.sf.sp.current

    
    @classmethod
    def SPT(cls, jobs, current=None):
        '''Dispatching rule: Shorted Processing Time First'''
        return (min(jobs, key=lambda j:j.PT),)
            
    @classmethod
    def SST(cls, jobs, current=None):
        '''Dispatching rule: Shorted Setup Time First.
        Provide a reasonably good solution for problems like TSP.
        A setup time matrix must be present.'''
        return (min(jobs, key=lambda j:current.getSetupTime(j)),)


class Allocation(Task):
    def __init__(self, name):
        Task.__init__(self, name)
    
    def __call__(self):
        keyFunc = lambda j:j.REQ
        jobs = sorted(self.input, key=keyFunc)          
        batch = []
        for i, g in itertools.groupby(jobs, key=keyFunc): #@UnusedVariable
            batch.append(list(g))
        for assignment in itertools.product(*batch):
            yield self.sf.sp.current.allocate(assignment)


class Process(Task):
    def __init__(self, name):
        Task.__init__(self, name)
    
    def __call__(self):
        self.sf.sp.current.process()  
        return [self.sf.sp.current]
          
                   
 
class Push(Task):
    '''
    The input of a push task should be a set of schedules
    '''
    def __init__(self, name):
        Task.__init__(self, name)
        self.max = -1
    
      
    def __call__(self):
        sp = self.sf.sp
        for sch in self.input:
            self.sf.stat.generated += 1
            if sch.isGoal() and (sp.best is None or sch.cost() < sp.best.cost()):
                sp.best = sch                    
                self.sf.stat.updated += 1
                #TODO: DO NOT PAUSE
                self.sf.timer.pause()
                self.sf.log('%s,%s,%s\n' % (self.sf.stat.expanded, sch.cost(), self.sf.timer.getSpan()))
                self.sf.timer.start()
                saveObj(sp.best, sp.directory + 'best.sch')
                print ('An improved schedule with %s'
                       '%s nodes are expanded\n') % (sp.best, self.sf.stat.expanded)
            elif sp.best is None or sch.cost() < sp.best.cost():
                heapq.heappush(sp.fringe, sch)
                if len(sp.fringe) > self.sf.stat.maxFringeLen:
                    self.sf.stat.maxFringeLen = len(sp.fringe)
                self.sf.stat.pushed += 1
            else:
                #Prune 
                self.sf.stat.deleted += 1
                del sch
            if 0 < self.max < len(sp.fringe):
                del sp.fringe[-1]

        
class Pop(Task):
    '''
    Pop of the first item in the priority fringe
    '''
    def __init__(self, name):
        Task.__init__(self, name) 
          
    def __call__(self):
        '''
        If fringe is empty, the execution will termination
        '''

        sp = self.sf.sp
        try:
            sp.current = heapq.heappop(sp.fringe)
            self.sf.stat.expanded += 1
        except IndexError:
            self.succs = [None]
            print 'Fringe is empty'
            
        if self.sf.debug: 
            print '*******Popped schedule**********' 
            print '%s\n%s' % (sp.current, sp.current.events())
            print '********************************'

